﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Agr.CQRS.Domain;
using Agr.CQRS.Infrastructure;
using Agr.CQRS.Infrastructure.Events;

namespace Agr.CQRS.Domain.Repository
{
    public class DomainRepository : IDomainRepository
    {
        DomainEventStorage domainEventStorage;

        public DomainRepository(DomainEventStorage domainEventStorage)
        {
            this.domainEventStorage = domainEventStorage;
        }

        public T GetById<T>(Guid id, int version) where T : AggregateRoot
        {
            return (T)GetById(id, version, typeof(T));
        }

        public AggregateRoot GetById(Guid id, int version, Type aggregateRootType)
        {
            AggregateRoot ar;

            if (version == 0)
            {
                ar = Activator.CreateInstance(aggregateRootType) as AggregateRoot;
                ar.CreateNew(id);
                return ar;
            }

            //try from cache first            
            ar = getFromCache(id, version);
            if (ar != null)
                return ar;

            //not in cache, recreate from events
            ar = recreate(id, version, aggregateRootType);

            cacheState(ar);
            return ar;
        }

        public void SaveChanges(AggregateRoot ar)
        {
            domainEventStorage.Store(ar.Aggregate.UnCommittedEvents);
        }

        private AggregateRoot recreate(Guid id, int version, Type aggregateRootType)
        {
            var ar = GetById(id, 0, aggregateRootType); //version 0 means a new object will be created

            var events = domainEventStorage.GetEventsToRebuildAggregateRootUpToVersion(id, version);

            foreach (var ev in events)
            {
                ev.Replaying = true;
                ar.Aggregate.Handle(ev);
            }

            return ar;
        }

        private Cache cache = new Cache();
        private void cacheState(AggregateRoot ar)
        {
            var ci = new CacheItem(ar, getCacheKey(ar));
            cache.AddItem(ci);

            //remove previous instances from cache
            for (var version = ar.Version - 1; version > 0; version--)
            {
                var key = getCacheKey(ar.Id, version);
                if (!cache.HasItem(key))
                    break;

                cache.RemoveItem(key);
            }
        }
        private string getCacheKey(AggregateRoot ar)
        {
            return getCacheKey(ar.Id, ar.Version);
        }
        private string getCacheKey(Guid id, int version)
        {
            return string.Format("{0}.{1}", id, version);
        }

        private AggregateRoot getFromCache(Guid id, int version)
        {
            var ci = cache.GetItem(getCacheKey(id, version));
            if (ci == null)
                return null;

            return ci.Value as AggregateRoot;
        }

    }
}
